import re
import sys

from go_node import *
from dump_log import *
from obo_parser import *

p_delim = re.compile(r'\W+|-|\.|_|,|;|:|\'|\"|/')

class go_parser:
	"""
	Parses go files and contains several utility functions
	If a dump file is given, it is parsed, otherwise a dump file is created from the GO XML file
	"""
	
	def __init__(self, go_dump_filename='go.dump', go_xml_filename='go.xml', go_obo_filename='go.obo', recreate=False):
		self.go_dump_filename = go_dump_filename
		self.go_xml_filename = go_xml_filename
		self.go_obo_filename = go_obo_filename
		self.go_tree = {} #tree containing the isa graph
		self.go_sym_tree = {} #tree containing the words-to-id map
		self.recreate = recreate
		self.max_depth = 5
		self.generate_trees()

	def generate_trees(self):
		"""
		Generates a hash table of GO ids and objects.
		Also generates another hash table for name lookups
		It first tries to find the obo file. If it is present it is parsed
		It obo file is not present, the dump file is looked up and parsed
		If both these files are not present, the xml file is parsed
		"""
		try:
			f = open(self.go_obo_filename, 'r')
			f.close()
			obo = obo_parser(self.go_obo_filename)
			self.go_tree = obo.parse()
			self.build_sym_table()
		except IOError:
			if not self.recreate:
				try:
					f = open(self.go_dump_filename, 'r') #do we have a dump file?
					f.close()
					#print "Found dump file"
					self.parse_dumpfile()
					self.build_sym_table()
				except:
					#print "Error parsing the dump file. Recreating it"
					d = dump_log(self.go_dump_filename)
					d.dump_xmlfile(self.go_xml_filename)
					self.parse_dumpfile()
					self.build_sym_table()
			else:
				d = dump_log(self.go_dump_filename)
				d.dump_xmlfile(self.go_xml_filename)
				self.parse_dumpfile()
				self.build_sym_table()
				
	def parse_dumpfile(self):
		"""
		Generates the GO Object Tree from the dumpfile
		The tree is a hash table with GO-id as the key and corresponding GO-object as the value
		GO-id here is an integer (for performance reasons instead of GO:XXXXXXX string)
		Hence all lookups should be using integer values or use get_go_object for looking up string IDs
		"""
		self.go_tree = {} #clear old tree if we are re-parsing
		file = open(self.go_dump_filename, 'r')
		contents = file.readlines()
		length = len(contents)
		line = 0
	
		while line < length:
			while(contents[line].strip()==""):
				line = line + 1
			#print 'parsing line', line
			go_id = int(contents[line])
			node = go_node(go_id)
			node['name'] = contents[line+1].strip()
			node['namespace'] = contents[line+2].strip()
			node['definition'] = contents[line+3].strip()
			node['isa'] = [int(x) for x in contents[line+4].strip().split()]
			line = line + 5
			self.go_tree[go_id] = node

	def build_sym_table(self):
		"""
		Symbol Table is a hash table in which the keys are words and the values are GO IDs containing that word
		For each GO term name, the words are split and the id is stored under each word
		"""
		self.go_sym_tree = {}

		#need an iterator here
		for go_id in self.go_tree.keys():
			words = self.get_unique(p_delim.split(self.go_tree[go_id]['name']))
			for word in words:
				if word!='':
					try:
						self.go_sym_tree[word.lower()].append(go_id)
					except KeyError:
						self.go_sym_tree[word.lower()] = [go_id]

	def get_unique(self, list):
		"""
		Returns the list of unique items in the given list
		However the order wont be maintained
		"""
		d = {}
		for l in list:
			d[l] = 1

		return d.keys()
		
	def get_go_object(self, id):
		"""
		Returns a GO object for the ID given
		ID can be of the form GO:XXXXXXX or XXXXXXX or a number
		Returns None if the object is not present
		"""
		if type(id) == type(''):
			l = id.split('GO:')
			if len(l) > 1:
				id = l[1]
		
			index = 0
			for index in reversed(range(0,len(id))):
				if id[index] == '0':
					pass
				else:
					break
			try:		
				id = int(id[:index+1])
			except ValueError:
				return None
		try:
			return self.go_tree[id]
		except KeyError:
			return None

	def get_prob_id(self, name):
		"""
		Gives a list of most likely IDs for a name
		The name need not be exact word in the GO file
		"""
		words = self.get_unique(p_delim.split(name))
		id_count = {}
	
		for word in words:
			try:
				ids = self.go_sym_tree[word.lower()]
				for id in ids:
					try:
						id_count[id] = id_count[id] + 1
					except KeyError:
						id_count[id] = 1
			except KeyError:
				pass

		prob = []
		max = 0

		for id, count in id_count.items():
			if count > max:
				max = count
			
	#	 print id_count
		for id, count in id_count.items():
			if count == max:
				prob.append(id)

		return prob

	def get_exact_id(self, name):
		"""
		Gives the GO ID for the name given or -1 if the ID is not present
		The name has to have the exact spelling as in GO file.
		Common symbols like dash, colon, comma etc. are ignored and the order of words can be changed
		"""
		
		words = self.get_unique(p_delim.split(name))
		id_count = {}
		
		for word in words:
			try:
				ids = self.go_sym_tree[word.lower()]
				for id in ids:
					try:
						id_count[id] = id_count[id] + 1
					except KeyError:
						id_count[id] = 1
			except KeyError:
				pass

		for id, count in id_count.items():
			#print id, p_delim.split(isa_tree[id]['name'])
			if len(words) == count and count == len(p_delim.split(self.go_tree[id]['name'])) - 1:
				return id

		return -1

	def get_ancestors(self, id, depth=0):
		"""
		Returns the list of ancestors till the root or the depth given of the given id
		If the depth given is 0, all the ancestor till the root are returned
		"""
		ancestors = []
		i = 0
		node = self.get_go_object(id)
		ancestors.extend(node['isa'])
		if node == None:
			return []
		else:
			while True:
				if i >= len(ancestors) or (depth !=0 and i>depth):
					break
				else:
					ancestors.extend(self.get_go_object(ancestors[i])['isa'])
					i += 1
		return ancestors

	def related(self, id1, id2, max_depth=-1):
		"""
		Return True if the objects have enough objects common in the GO tree
		"""
		if max_depth == -1:
			max_depth = self.max_depth
			
		node1 = self.get_go_object(id1)
		node2 = self.get_go_object(id2)

		j = 0

		#get all the relatives of id1
		queue1 = node1['isa']
		i = max_depth
			
		count = [] #count of the number of children added

		while queue1 and i:
			if j >= len(queue1):
				break
			e = queue1[j]
			j = j + 1
			children = self.go_tree[e]['isa']
			count.append(len(children))
			queue1.extend(children)
			if j == count[0]:
				count.remove(count[0])
				j = 0
				i = i - 1

		#compare with all the relatives of id2
		queue2 = node2['isa']
		#print queue2
		i = max_depth
		j = 0

		while queue2 and i:
			#print queue2
			if j >= len(queue2):
				break
			e = queue2[j]
			j = j + 1
			children = self.go_tree[e]['isa']
			count.append(len(children))
			queue2.extend(children)
			if j == count[0]:
				count.remove(count[0])
				j = 0
				i = i - 1

		for i in queue1:
			if i in queue2:
				return True

		return False